package airAgent.group5;

import airAgent.group5.ontology.ontology.AirAgentOntology;
import airAgent.group5.ontology.ontology.BuyTicket;
import airAgent.group5.ontology.ontology.CancelTicket;
import airAgent.group5.ontology.ontology.CheckTicketPrice;
import airAgent.group5.ontology.ontology.Direction;
import airAgent.group5.ontology.ontology.Money;
import airAgent.group5.ontology.ontology.ReturnTicketPrice;
import airAgent.group5.ontology.ontology.Ticket;
import jade.content.Concept;
import jade.content.ContentElement;
import jade.content.lang.Codec;
import jade.content.lang.Codec.CodecException;
import jade.content.lang.sl.SLCodec;
import jade.content.onto.Ontology;
import jade.content.onto.OntologyException;
import jade.content.onto.UngroundedException;
import jade.content.onto.basic.Action;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.SimpleBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Scanner;

import javax.sound.midi.SysexMessage;

public class Client extends Agent {

    private static String NAME = "C5";
    private static String AIRLINE = "AIRLINE";
    airAgent.group5.ontology.ontology.Airline ontoAirline = new airAgent.group5.ontology.ontology.Airline();
    private Codec codec = new SLCodec();
    private Ontology ontology = AirAgentOntology.getInstance();
    //for buying a ticket
    private ArrayList<Ticket> tickets = new ArrayList<Ticket>();   // ticket to buy
    private ArrayList<CheckTicketPrice> checkTickets = new ArrayList<CheckTicketPrice>();
    private ArrayList ticketAirlineSellers = null;
    private int minStay = ConfigReader.getInstance().getMinStay();
    private int maxStay = ConfigReader.getInstance().getMaxStay();
    private ArrayList<Ticket> boughtTickets = new ArrayList<Ticket>(); //Here are all bought tickets!
    private Integer date=1;
    private boolean spam = false; // debug output

    private class ResponseCancelTicketBehaviour extends SimpleBehaviour {

        boolean done = false;
        int place = 0;
        CancelTicket theCancelledTicket = null;

        private boolean findTicket(CancelTicket canceledticket) {
            place = 0;
            for (Ticket t : boughtTickets) {
                if ((t.getTicketDate() == (canceledticket.getTicketDate())) && (t.getTicketDirection().getValue() == (canceledticket.getTicketDirection().getValue())) && (t.getTicketSeats() == canceledticket.getTicketSeats())) {
                    return true;
                }
                place++;
            }
            return false;
        }

        public ResponseCancelTicketBehaviour(CancelTicket ticket) {
            theCancelledTicket = ticket;
        }

        public void action() {
            ACLMessage msg = receive(MessageTemplate.MatchProtocol(AirAgentOntology.CANCELTICKET));
            if (msg != null) {
                done = true;
                if (msg.getPerformative() == ACLMessage.INFORM) {
//                	try {
//						ContentElement ce = getContentManager().extractContent(msg);
//						if (ce instanceof Action) {
//							Action agAction = (Action) ce;
//	                    	Concept conc = agAction.getAction();
//	                    	if (conc instanceof Ticket){
//	                    		Ticket canceledTicket = (Ticket) conc;
//	                    		if(theCancelledTicket.getTicketAirlineID() == null){
//	                    			theCancelledTicket.setTicketAirlineID(msg.getSender());
//	                    		}
                    if (findTicket(theCancelledTicket)) {
                        boughtTickets.remove(place);
                        if(spam) System.out.println(getLocalName() + ": CancelTicket -> The ticket for the date " + theCancelledTicket.getTicketDate() + " was canceled and deleted!");
                        //System.out.println(getLocalName() + ": CancelTicket(Remove) -> Didn't found the Ticket in the list, not deleted from he client!");
                    } else {
                    	if(spam) System.out.println(getLocalName() + ": CancelTicket(Search) -> Didn't found the Ticket in the list, not deleted from he client!");
                    }
//	                    	}
//						}
//					} catch (UngroundedException e) {
//						e.printStackTrace();
//					} catch (CodecException e) {
//						e.printStackTrace();
//					} catch (OntologyException e) {
//						e.printStackTrace();
//					}
                } else if (msg.getPerformative() == ACLMessage.REFUSE) {
                    //TODO: ask another agent with a worse price or date
                	//To complicated to do right now, Alex 05.06.
                	if(spam) System.out.println(getLocalName() + ": CancelTicket INFORM -> The Airline sents a REFUSE, coudn't cancel ticket.");
                } else if (msg.getPerformative() == ACLMessage.NOT_UNDERSTOOD) {
                	if(spam) System.out.println(getLocalName() + ": CancelTicket INFORM -> The Airline sents a NOT_UNDERSTOOD, coudn't cancel ticket.");
                }
            } else {
                block();
            }
        }

        @Override
        public boolean done() {
            return done;
        }
    }

    private class CancelTicketBehaviour extends SimpleBehaviour {

        boolean done = false;
        CancelTicket cancelTicket = new CancelTicket();
        AID adress = new AID();

        public CancelTicketBehaviour(Ticket cancelTicket) {
            this.cancelTicket.setTicketDate(cancelTicket.getTicketDate());
            this.cancelTicket.setTicketDirection(cancelTicket.getTicketDirection());
            this.cancelTicket.setTicketSeats(cancelTicket.getTicketSeats());
            adress = cancelTicket.getTicketAirlineID();
        }

        public void action() {
            if (boughtTickets.size() != 0) {
                if (boughtTickets.get(0).getTicketAirlineID() != null) {
                	if(spam) System.out.println(getLocalName() + ": Trying to cancel ticket!");


                    Action cancelTicketAction = new Action(adress, cancelTicket);
                    ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
                    msg.addReceiver(adress);
                    msg.setLanguage(codec.getName());
                    msg.setOntology(ontology.getName());
                    msg.setProtocol(AirAgentOntology.CANCELTICKET);

                    try {
                        getContentManager().fillContent(msg, cancelTicketAction);
                        send(msg);
                        if(spam) System.out.println(getLocalName() + ": Send a ticket cancel request to " + boughtTickets.get(0).getTicketAirlineID().getLocalName() + " to cancel the ticket from the " + boughtTickets.get(0).getTicketDate() + ".");
                    } catch (CodecException e) {
                        e.printStackTrace();
                    } catch (OntologyException e) {
                        e.printStackTrace();
                    }
                    done = true;
                    addBehaviour(new ResponseCancelTicketBehaviour(cancelTicket));
                } else {
                	if(spam) System.out.println(getLocalName() + ": Error in CancelTicketBehaviour: The getTicketAirlineID() was not set by the Airline! Please do so, this is important for or client. Exit.");
                }
            } else {
            	if(spam) System.out.println(getLocalName() + ": No ticket to cancel!");
                doWait(1000);
            }
        }

        public boolean done() {
            return done;
        }
    }

    private class BuyTicketBehaviour extends OneShotBehaviour {

        public void action() {
            if (tickets.size() == 0) {
                System.err.println(getLocalName() + ": BuyTicketBehaviour ERROR -> No tickets to buy?!");
                return;
            } else {
                Ticket ticket = tickets.remove(0); //Gets the first ticket and delets it

                ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
                msg.addReceiver(ticket.getTicketAirlineID());
                msg.setLanguage(codec.getName());
                msg.setOntology(ontology.getName());
                msg.setProtocol(AirAgentOntology.BUYTICKET);

                BuyTicket buyTicketAction = new BuyTicket();
                buyTicketAction.setTicketDate(ticket.getTicketDate());
                buyTicketAction.setTicketDirection(ticket.getTicketDirection());
                buyTicketAction.setTicketSeats(ticket.getTicketSeats());
                Action btAction = new Action(ticket.getTicketAirlineID(), buyTicketAction);
                try {
                    getContentManager().fillContent(msg, btAction);
                    if(spam) System.out.println(getLocalName() + ": trying to buy a ticket");
                    send(msg);
                } catch (CodecException ce) {
                    ce.printStackTrace();
                } catch (OntologyException oe) {
                    oe.printStackTrace();

                }
                addBehaviour(new ResponseBuyTicketBehaviour());

            }


        }
    }

    private class ResponseBuyTicketBehaviour extends SimpleBehaviour {

        private boolean end = false;

        @Override
        public void action() {
            ACLMessage msg = receive(MessageTemplate.MatchProtocol(AirAgentOntology.BUYTICKET));
            if (msg != null) {
                end = true;
                if (msg.getPerformative() == ACLMessage.INFORM) {
                    try {
                        ContentElement ce = getContentManager().extractContent(msg);
                        if (ce instanceof Action) {
                            Action agAction = (Action) ce;
                            Concept conc = agAction.getAction();
                            if (conc instanceof BuyTicket) {
                                //We recieve the actual ticket from the airline. Just copy every field to our local ticket and put the local ticket in the ArrayList
                                Ticket ticket = new Ticket();
                                BuyTicket bTicket = (BuyTicket) conc;
                                //First the information from the message header
                                ticket.setTicketClientID(getAID());
                                ticket.setTicketAirlineID(msg.getSender());
                                //Second the informations from the message body
                                ticket.setTicketDate(bTicket.getTicketDate());
                                ticket.setTicketDirection(bTicket.getTicketDirection());
                                ticket.setTicketSeats(bTicket.getTicketSeats());
                                //Third the price? But from where?
//	                    		ticket.setTicketPrice(value)

                                boughtTickets.add(ticket);
                                int cancellationProb = (int)(Math.random() * 10);
                                if (cancellationProb > 8)
                                    addBehaviour(new CancelTicketBehaviour(ticket)); //testing the cancel
                                if(spam) System.out.println(myAgent.getLocalName() + ": ResponseBuyTicketBehaviour INFORM -> Ticket bought for:" + ticket.getTicketDate());
                            } else {
                                System.err.println(getLocalName() + ": ResponseBuyTicketBehaviour ERROR -> Received != BuyTicket object!");
                            }
                        }
                    } catch (UngroundedException e) {
                        e.printStackTrace();
                    } catch (CodecException e) {
                        e.printStackTrace();
                    } catch (OntologyException e) {
                        e.printStackTrace();
                    }

                } else if (msg.getPerformative() == ACLMessage.REFUSE) {
                	if(spam) System.out.println(myAgent.getLocalName() + ": ResponseBuyTicketBehaviour INFORM -> Couldn't bought ticket, received REFUSE.");
                } else if (msg.getPerformative() == ACLMessage.NOT_UNDERSTOOD) {
                	if(spam) System.out.println(myAgent.getLocalName() + ": ResponseBuyTicketBehaviour INFORM -> Couldn't bought ticket, received NOT_UNDERSTOOD.");
                }
            } else {
                block();
            }
        }

        @Override
        public boolean done() {
            return end;
        }
    }

    private class ReceiveTicketPriceBehaviour extends SimpleBehaviour {

        ArrayList<Ticket> returnedTickets = new ArrayList<Ticket>();
        CheckTicketPrice oneRequestedTicket = null;
        int answers = 0;

        @Override
        public void action() {
            if (oneRequestedTicket == null) {
//            	System.out.println(checkTickets.size());
                oneRequestedTicket = checkTickets.remove(0);//Get the first requested ticket from the List.
            }
            ACLMessage msg = receive(MessageTemplate.MatchProtocol(AirAgentOntology.RETURNTICKETPRICE));
            if (msg != null) {
                try {
                    ContentElement ce = null;
                    if (msg.getPerformative() == ACLMessage.INFORM) {
                        // The ContentManager transforms the message content (string)
                        // in java objects
                        ce = getContentManager().extractContent(msg);
                        // We expect an action inside the message
                        if (ce instanceof Action) {
                            Action agAction = (Action) ce;
                            Concept conc = agAction.getAction();
                            // If the action is ReturnTicketPrice
                            if (conc instanceof ReturnTicketPrice) {
                                ReturnTicketPrice rtp = (ReturnTicketPrice) conc;
                                Ticket returnedTicket = rtp.getPrice();

                                if ((oneRequestedTicket.getTicketDate() == (returnedTicket.getTicketDate())) && (oneRequestedTicket.getTicketDirection().getValue() == returnedTicket.getTicketDirection().getValue())) {
                                    Ticket ticket = new Ticket();
                                    //Copy the 3 "must have"-fields from the returned ticket
                                    ticket.setTicketDate(returnedTicket.getTicketDate());
                                    ticket.setTicketPrice(returnedTicket.getTicketPrice());
                                    ticket.setTicketDirection(returnedTicket.getTicketDirection());
                                    //Copy the rest from the requsteded or from other sources
                                    ticket.setTicketSeats(oneRequestedTicket.getTicketSeats());
                                    ticket.setTicketAirlineID(msg.getSender());
//                            		ticket.setTicketClientID(getAID()); //Change when new Ontology is ready

                                    returnedTickets.add(ticket);
                                    answers++;
//                            		Money price = returnedTicket.getTicketPrice();
//                            		if (bestPrice == null || bestPrice.getValue() < price.getValue()) {
//                            			bestPrice = price;
//                            			ticket.setTicketPrice(bestPrice);
//                            			ticketAirlineSeller = msg.getSender();
//                            		}
                                    if(spam) System.out.println(getLocalName() + ": has received ticket price " + ticket.getTicketPrice().getValue() + " from " + msg.getSender().getLocalName() + "for the date " + ticket.getTicketDate());
                                } else {
                                    //The requested Ticket is not equal the returned Ticket. This means another Instance of ReceiveTicketPriceBehaviour is waiting for this message.
                                    send(msg);
                                }
                            } else {
                                System.err.println(getLocalName() + ": ReceiveTicketPriceBehaviour ERROR (Date= " + oneRequestedTicket.getTicketDate() + ")-> Received an object != Ticket from " + msg.getSender().getLocalName() + ". The Airline has to send a Ticket, see protocol from 23.05. request 4.");
                                answers++; //Count as an answere.
                            }
                        }
                    } else if (msg.getPerformative() == ACLMessage.REFUSE) {
//                        ce = getContentManager().extractContent(msg);
//                        if (ce instanceof ReturnTicketPrice) {
                    	if(spam) System.out.println(getLocalName() + ": ReceiveTicketPriceBehaviour INFORM -> Received REFUSE, counted as invalid ticket.");
                            answers++;
//                        }
                    } else if (msg.getPerformative() == ACLMessage.NOT_UNDERSTOOD) {
                    	if(spam) System.out.println(getLocalName() + ": ReceiveTicketPriceBehaviour INFORM -> Received NOT_UNDERSTOOD, counted as invalid ticket.");
                       	answers++;
                    }
                } catch (CodecException e) {
                    e.printStackTrace();
                } catch (OntologyException oe) {
                    oe.printStackTrace();
                }
            }

        }

        @Override
        public boolean done() {
            if (answers == ticketAirlineSellers.size()) {
                //Now we check here the cheapest flight and set it:
                float price = -1;
                Ticket bestPrice = null;
                for (Ticket t : returnedTickets) {
                    if ((price == -1) || (t.getTicketPrice().getValue() < price)) {
                        price = t.getTicketPrice().getValue();
                        bestPrice = t;
                    }
                }
                if (bestPrice != null) {
                    tickets.add(bestPrice);
                    String wichWay = (bestPrice.getTicketDirection().getValue() == true ? "GO" : "Return");
                    if(spam) System.out.println(getLocalName() + ": ReceiveTicketPriceBehaviour INFORM -> The cheapest flight for the " + bestPrice.getTicketDate() + "(" + wichWay + ") is " + price + " Euro from airline " + bestPrice.getTicketAirlineID().getLocalName());
                    //CHECK : if any is down airline agent is down after asking should be
                    // remove from ticketAirlineSellers 'cause if not client will be blocked
                    addBehaviour(new BuyTicketBehaviour());
                } else {
                    //Whenn all answers are wrong.
                    if(spam) System.err.println(getLocalName() + ": ReceiveTicketPriceBehaviour ERROR -> could not calculate, price! all answers where invalid, due to not understood objects or refuses from the airline. Don't buy a ticket.");
                }
                return true;
            } else {
                return false;
            }
        }
    }

    private class AskForTicketPriceBehaviour extends OneShotBehaviour {

        private int flightDay = -1;
        private boolean isReturnFly = false;
        private boolean dirValue = true;;
        private int seats = 1;
        private boolean akedAlready = false; //We need to check if we already created a ReceiveTicketPriceBehaviour or added the desired ticket to the ticket list

        public void action() {
        	Direction dir = new Direction();
            AID airlineRef;
            if(spam) System.out.println(getLocalName() + ": try to find an airline for asking for tickets!");
            // Creates the description for the type of agent to be searched
            DFAgentDescription dfd = new DFAgentDescription();
            ServiceDescription sd = new ServiceDescription();
            sd.setType(AIRLINE);
            dfd.addServices(sd);
            ticketAirlineSellers = new ArrayList();

            try {
                // It finds agents of the required type
                DFAgentDescription[] res = new DFAgentDescription[1];
                res = DFService.search(myAgent, dfd);

                for (DFAgentDescription dfdAgent : res) {
                    airlineRef = dfdAgent.getName();
                    if(spam) System.out.println(getLocalName() + ": Found Airline for asking for tickets:" + airlineRef.getLocalName());
                    ticketAirlineSellers.add(airlineRef);
                    ACLMessage msg = new ACLMessage(ACLMessage.QUERY_REF);
                    msg.addReceiver(airlineRef);
                    msg.setLanguage(codec.getName());
                    msg.setOntology(ontology.getName());
                    msg.setContent(NAME);
                    msg.setProtocol(AirAgentOntology.CHECKTICKETPRICE);
                   
                    CheckTicketPrice checkTicket = new CheckTicketPrice();

//                    CheckTicketPrice ctpAction = new CheckTicketPrice();
                    dir.setValue(dirValue);
                    if (!isReturnFly) {
                        checkTicket.setTicketDate(getFlightDate());
                    } else {
                        checkTicket.setTicketDate(flightDay);
                    }

                    checkTicket.setTicketDirection(dir);
                    checkTicket.setTicketSeats(seats);
                    Action askForTicketAction = new Action(airlineRef, checkTicket);
                    try {
                        // The ContentManager transforms the java objects into strings
                        getContentManager().fillContent(msg, askForTicketAction);//Just use the Action for a wrapper!
                        send(msg);
                        if (!akedAlready) {//add the ticket and the behavior just once, not for every airline
                            addBehaviour(new ReceiveTicketPriceBehaviour());
                            checkTickets.add(checkTicket);
                            akedAlready = true;
                        }
                        String directionString = (dir.getValue() == true ? "GO" : "Return");
                        if(spam) System.out.println(getLocalName() + ": asked " + airlineRef.getLocalName() + " for a flight for " + checkTicket.getTicketDate() + " and direction " + directionString);

                    } catch (CodecException ce) {
                        ce.printStackTrace();
                    } catch (OntologyException oe) {
                        oe.printStackTrace();
                    }

                }
                //Not to be executed for every Airline seperate
                if (!isReturnFly) {
                    action(getFlyBackDate(3), !dirValue, seats);
                }


            } catch (Exception e) {
            }
        }

        private void action(int flyBackDate, boolean direction, int seats) {
            flightDay = flyBackDate;
            dirValue = direction;
            this.seats = seats;
            isReturnFly = true;
            akedAlready = false; //reset for new ticket
            action();
        }
    }

    private int getFlightDate() {
        //This has to work with the timer as well.
        //Only days between 1 - 31, not years or month
        return date +2;
    }

    private int getFlyBackDate(int days) {
        int today = getFlightDate();


        //I'll accept strange days like 32 of Feburary. Don't want to consider that not.
        return today + 3; //3000000 == 3 Days
    }



    protected void setup() {
        Object [] args = getArguments();
        date = Integer.parseInt(args[0].toString());
        if(spam) System.out.println(getLocalName() + ": New client created");
        getContentManager().registerLanguage(codec);
        getContentManager().registerOntology(ontology);
        addBehaviour(new AskForTicketPriceBehaviour());
    }
}
